/***************************************************************************//**
 * @file HID/HID_Joysticks.cpp
 * @author  Marek M. Cel <marekcel@mscsim.org>
 *
 * @section LICENSE
 *
 * Copyright (C) 2013 Marek M. Cel
 *
 * This file is part of MScSim. You can redistribute and modify it under
 * the terms of GNU General Public License as published by the Free Software
 * Foundation; either version 3 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.
 * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * Further information about the GNU General Public License can also be found
 * on the world wide web at http://www.gnu.org.
 ******************************************************************************/
#ifndef HID_JOYSTICKS_CPP
#define HID_JOYSTICKS_CPP
#endif

////////////////////////////////////////////////////////////////////////////////

#include <cmath>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string>

#ifdef _LINUX_
#   include <fcntl.h>
#   include <linux/joystick.h>
#endif

#ifdef WIN32
#   ifdef MSCSIM_DIRECTINPUT
#       include <dinput.h>
#   else
#       include <Windows.h>
#   endif
#endif

#include <HID/HID_Joysticks.h>

////////////////////////////////////////////////////////////////////////////////

HID_Joysticks *HID_Joysticks::m_instance = 0;

////////////////////////////////////////////////////////////////////////////////

float forceX [ MSCSIM_MAX_JOYS_COUNT ]; ///< -1.0 ... 1.0 normalized x-axis force
float forceY [ MSCSIM_MAX_JOYS_COUNT ]; ///< -1.0 ... 1.0 normalized y-axis force

int joysCount = 0;  ///< number of active joysticks

HID_Joysticks::Data joyData [ MSCSIM_MAX_JOYS_COUNT ];  ///< joysticks data

#ifdef MSCSIM_DIRECTINPUT
LPDIRECTINPUT8  di; ///<

LPDIRECTINPUTDEVICE8 joysDI [ MSCSIM_MAX_JOYS_COUNT ] = { 0 };  ///<
LPDIRECTINPUTEFFECT  effect [ MSCSIM_MAX_JOYS_COUNT ] = { 0 };  ///<
#endif

#if defined(WIN32) && !defined(MSCSIM_DIRECTINPUT)
UINT        joysId  [ MSCSIM_MAX_JOYS_COUNT ];  ///<
JOYINFOEX   joyInfo [ MSCSIM_MAX_JOYS_COUNT ];  ///<
#endif

#ifdef _LINUX_
int         joysFD [ MSCSIM_MAX_JOYS_COUNT ] = { 0 };   ///<
js_event    joyEvent;                                   ///<
#endif

////////////////////////////////////////////////////////////////////////////////

#ifdef MSCSIM_DIRECTINPUT
BOOL CALLBACK enumJoysCallback( const DIDEVICEINSTANCE *devInst, VOID *context );
BOOL CALLBACK enumAxesCallback( const DIDEVICEOBJECTINSTANCE *devObjInst, VOID *context );
#endif

////////////////////////////////////////////////////////////////////////////////

using namespace std;

////////////////////////////////////////////////////////////////////////////////

HID_Joysticks::~HID_Joysticks()
{
#   ifdef MSCSIM_DIRECTINPUT
    for ( int i = 0; i < joysCount && i < MSCSIM_MAX_JOYS_COUNT; i++ )
    {
        if ( joysDI[ i ] ) joysDI[ i ]->Unacquire();
        if ( joysDI[ i ] ) joysDI[ i ]->Release();
    }

    di->Release();
#   endif
}

////////////////////////////////////////////////////////////////////////////////

#ifdef MSCSIM_DIRECTINPUT
void HID_Joysticks::init( void *winId )
{
    HWND winId = *((HWND*)winId);

    if (
         FAILED( DirectInput8Create( GetModuleHandle( NULL ), DIRECTINPUT_VERSION, IID_IDirectInput8, (VOID**)&di, NULL ) )
       )
    {
        cerr << "WARNING: Creating DirectInput failed." << endl;
        return;
    }

    // enuming joysticks
    if ( 
         FAILED( di->EnumDevices( DI8DEVCLASS_GAMECTRL, enumJoysCallback, NULL, DIEDFL_ATTACHEDONLY ) )
       )
    {
        cerr << "WARNING: Enumerating devices failed." << endl;
        return;
    }

    // for every enumerated joys
    for( int i = 0; i < joysCount && i < MSCSIM_MAX_JOYS_COUNT; i++ )
    {
        // ---
        DIDEVICEINSTANCE devInst;
        devInst.dwSize = sizeof( DIDEVICEINSTANCE );

        if (
             FAILED( joysDI[ i ]->GetDeviceInfo( &devInst ) )
           )
        {
            cerr << "WARNING: Getting #" << i << " device info failed." << endl;
        }
        else
        {
            char tempName[ 522 ];
            size_t convertedChars = 0;

            wcstombs_s( NULL, tempName, 522, devInst.tszProductName, _TRUNCATE );
            
            joyData[ i ].name = tempName;
        }

        // ---
        if (
             FAILED( joysDI[ i ]->SetDataFormat( &c_dfDIJoystick ) )
           )
        {
            cerr << "WARNING: Setting data format of " << joyData[ i ].name << " failed." << endl;
            return;
        }

        // ---
        if ( 
             FAILED( joysDI[ i ]->SetCooperativeLevel( m_winId, DISCL_EXCLUSIVE | DISCL_BACKGROUND ) )
           )
        {
            cerr << "WARNING: Setting cooperation level of " << joyData[ i ].name << " failed." << endl;
            return;
        }

        // ---
        DIDEVCAPS caps;
        caps.dwSize = sizeof( DIDEVCAPS );

        if (
             FAILED( joysDI[ i ]->GetCapabilities( &caps ) )
           )
        {
            cerr << "WARNING: Getting capabilities of " << joyData[ i ].name << " failed." << endl;
            return;
        }
        else
        {
            joyData[ i ].axisCount = caps.dwAxes;
            joyData[ i ].buttCount = caps.dwButtons;
            joyData[ i ].force     = ( caps.dwFlags & DIDC_FORCEFEEDBACK ) ? 1 : 0;
            
            //if ( joyData[ i ].force )
            //{
            //    cerr << joyData[ i ].name << " is Force Feedback capable device. " << endl;
            //}
        }

        // ---
        if (
             FAILED( joysDI[ i ]->EnumObjects( enumAxesCallback, joysDI[ i ], DIDFT_AXIS ) )
           )
        {
            return;
        }

        joyData[ i ].active = 1;

        if ( joyData[ i ].force ) initForces( i );
    }
}
#endif

////////////////////////////////////////////////////////////////////////////////

#if defined(WIN32) && !defined(MSCSIM_DIRECTINPUT)
void HID_Joysticks::init( void * )
{
    UINT    joyIdTemp = JOYSTICKID1;
    JOYCAPS joyCaps;

    for ( int i = 0; i < MSCSIM_MAX_JOYS_COUNT; i++ )
    {
        if ( joysCount > (int)joyGetNumDevs() ) break;

        if ( joyGetDevCaps( joyIdTemp, &joyCaps, sizeof(JOYCAPS) ) == JOYERR_NOERROR )
        {
            joysId[ joysCount ] = joyIdTemp;

            char    tempNameChar[ 255 ];
            wstring tempNameWstr;

            tempNameWstr = joyCaps.szPname;
            
#           ifdef _MSC_VER
            strcpy_s ( tempNameChar, (char*)tempNameWstr.c_str() );
#           else
            strcpy   ( tempNameChar, (char*)tempNameWstr.c_str() );
#           endif

            joyData[ joysCount ].name = tempNameChar;

            joyData[ joysCount ].axisCount = joyCaps.wNumAxes;
            joyData[ joysCount ].buttCount = joyCaps.wNumButtons;

            joyData[ joysCount ].active = 1;
            joyData[ joysCount ].force  = 0;

            joysCount++;
        }

        joyIdTemp++;
    }
}
#endif

////////////////////////////////////////////////////////////////////////////////

#ifdef _LINUX_
void HID_Joysticks::init( void * )
{
    for ( int i = 0; i < MSCSIM_MAX_JOYS_COUNT; i++ )
    {
        char tempDev[ 255 ];
        
        sprintf( tempDev, "/dev/input/js%d", i );

        int joyFD = open( tempDev, O_NONBLOCK );

        if ( joyFD > 0 )
        {
            joysFD[ joysCount ] = joyFD;

            char tempName[ 522 ];
            char tempCount;

            ioctl( joysFD[ joysCount ], JSIOCGAXES,     &tempCount );  joyData[ joysCount ].axisCount = min( (int)tempCount, MSCSIM_MAX_AXES_COUNT );
            ioctl( joysFD[ joysCount ], JSIOCGBUTTONS,  &tempCount );  joyData[ joysCount ].buttCount = min( (int)tempCount, MSCSIM_MAX_BUTT_COUNT );
            ioctl( joysFD[ joysCount ], JSIOCGNAME(64), tempName  );

            joyData[ joysCount ].name = tempName;

            joyData[ joysCount ].active = 1;
            joyData[ joysCount ].force  = 0;

            joysCount++;
        }
    }
}
#endif

////////////////////////////////////////////////////////////////////////////////

#ifdef MSCSIM_DIRECTINPUT
void HID_Joysticks::update()
{
    HRESULT    hResult;
    DIJOYSTATE JoyState;

    for( int i = 0; i < joysCount && i < MSCSIM_MAX_JOYS_COUNT; i++ )
    {
        // ---
        hResult = joysDI[ i ]->Poll();

        if ( FAILED( hResult ) )
        {
            hResult = joysDI[ i ]->Acquire();

            while ( hResult == DIERR_INPUTLOST )
            {
                hResult = joysDI[ i ]->Acquire();
            }

            return;
        }

        // ---
        hResult = joysDI[ i ]->GetDeviceState( sizeof( DIJOYSTATE ), &JoyState );

        if( FAILED( hResult ) )
        {
            return; // The device should have been acquired during the Poll()
        }

        // Axes
        joyData[ i ].afAxis[ MSCSIM_JOY_AXIS_X ]  = (float)JoyState.lX  / MSCSIM_AXIS_RANGE;
        joyData[ i ].afAxis[ MSCSIM_JOY_AXIS_Y ]  = (float)JoyState.lY  / MSCSIM_AXIS_RANGE;
        joyData[ i ].afAxis[ MSCSIM_JOY_AXIS_Z ]  = (float)JoyState.lZ  / MSCSIM_AXIS_RANGE;

        // Rotations
        joyData[ i ].afAxis[ MSCSIM_JOY_AXIS_RX ] = (float)JoyState.lRx / MSCSIM_AXIS_RANGE;
        joyData[ i ].afAxis[ MSCSIM_JOY_AXIS_RY ] = (float)JoyState.lRy / MSCSIM_AXIS_RANGE;
        joyData[ i ].afAxis[ MSCSIM_JOY_AXIS_RZ ] = (float)JoyState.lRz / MSCSIM_AXIS_RANGE;

        // Sliders
        joyData[ i ].afAxis[ MSCSIM_JOY_AXIS_S1 ] = (float)JoyState.rglSlider[ 0 ] / MSCSIM_AXIS_RANGE;
        joyData[ i ].afAxis[ MSCSIM_JOY_AXIS_S2 ] = (float)JoyState.rglSlider[ 1 ] / MSCSIM_AXIS_RANGE;

        // POVs
        for ( int p_iter = 0; p_iter < 4; p_iter++ )
        {
            int iHorIndex = MSCSIM_JOY_AXIS_P1 + 2 * p_iter;
            int iVerIndex = MSCSIM_JOY_AXIS_P2 + 2 * p_iter;

            if ( JoyState.rgdwPOV[ p_iter ] == -1 )
            {
                joyData[ i ].afAxis[ iHorIndex ] = 0.0;
                joyData[ i ].afAxis[ iVerIndex ] = 0.0;
            }
            else
            {
                float fA_deg = (float)JoyState.rgdwPOV[ p_iter ] / (float)DI_DEGREES;
                float fA_rad = M_PI * fA_deg / 180.0f;

                float fSinA = sin( dA_rad );
                float fCosA = cos( dA_rad );
                
                fSinA = (  fabs( fSinA ) > 0.01f ) ? fSinA : 0.0f;
                fCosA = (  fabs( fCosA ) > 0.01f ) ? fCosA : 0.0f;

                joyData[ i ].afAxis[ iHorIndex ] = sgn( fSinA );
                joyData[ i ].afAxis[ iVerIndex ] = sgn( fCosA );
            }
        }

        // Buttons
        for ( int b_iter = 0; b_iter < MSCSIM_MAX_BUTT_COUNT; b_iter++ )
		{ 
            joyData[ i ].aiButt[ b_iter ] = ( JoyState.rgbButtons[ b_iter ] & 0x80 ) >> 7;
		} 

        // Force Feedback
        if ( joyData[ i ].force ) updateForces( i );
    }
}
#endif

////////////////////////////////////////////////////////////////////////////////

#ifdef _LINUX_
void HID_Joysticks::update()
{

}
#endif

////////////////////////////////////////////////////////////////////////////////

int HID_Joysticks::getJoysCount() const
{
    return joysCount;
}

////////////////////////////////////////////////////////////////////////////////

HID_Joysticks::Data HID_Joysticks::getJoyData( int iJoyNum ) const
{
    if ( iJoyNum < MSCSIM_MAX_JOYS_COUNT )
    {
        return joyData[ iJoyNum ];
    }
    else
    {
        cerr << "Error in HID_Joysticks::getJoyData( int ) const: Wrong joystick index number." << endl;
        exit( EXIT_FAILURE );
    }
}

////////////////////////////////////////////////////////////////////////////////

#ifdef MSCSIM_FORCEFEEDBACK
void HID_Joysticks::setEffectsEnabled( int iEnabled )
{
#   ifdef MSCSIM_DIRECTINPUT
    for ( int i = 0; i < MSCSIM_MAX_JOYS_COUNT; i++ )
    {
        afForceX[ i ] = 0.0f;
        afForceY[ i ] = 0.0f;

        if ( iEnabled )
        {
            if ( pEffect[ i ] ) pEffect[ i ]->Start( 1, 0 );
        }
        else
        {
            if ( pEffect[ i ] ) pEffect[ i ]->Stop();
        }
    }
#   endif
}
#endif

////////////////////////////////////////////////////////////////////////////////

#ifdef MSCSIM_FORCEFEEDBACK
void HID_Joysticks::setEffectsMagnitude( int iJoyNum, float fNormX, float fNormY )
{
#   ifdef MSCSIM_DIRECTINPUT
    if ( iJoyNum < MSCSIM_MAX_JOYS_COUNT )
    {
        afForceX[ iJoyNum ] = - range( -1.0f , 1.0f , fNormX );
        afForceY[ iJoyNum ] = - range( -1.0f , 1.0f , fNormY );
    }
    else
    {
        cerr << "Error in HID_Joysticks::setEffectsMagnitude( int, float, float ):  Wrong joystick index number." << endl;
        exit( EXIT_FAILURE );
    }
#   endif
}
#endif

////////////////////////////////////////////////////////////////////////////////

HID_Joysticks::HID_Joysticks()
{
    for ( int i = 0; i < MSCSIM_MAX_JOYS_COUNT; i++ )
    {
        forceX[ i ] = 0.0f;
        forceY[ i ] = 0.0f;

        joyData[ i ].name = "";

        for ( int a = 0; a < MSCSIM_MAX_AXES_COUNT; a++ ) joyData[ i ].axis[ a ] = 0.0f;
        for ( int b = 0; b < MSCSIM_MAX_BUTT_COUNT; b++ ) joyData[ i ].butt[ b ] = 0;

        joyData[ i ].axisCount = 0;
        joyData[ i ].buttCount = 0;

        joyData[ i ].active = 0;
        joyData[ i ].force  = 0;

#       ifdef _LINUX_
        joysFD[ i ] = 0;
#       endif

#       ifdef MSCSIM_DIRECTINPUT
        joysDI  [ i ] = 0;
        pEffect [ i ] = 0;
#       endif

#       if defined(WIN32) && !defined(MSCSIM_DIRECTINPUT)
        joysId[ i ] = 0;
#       endif
    }
}

////////////////////////////////////////////////////////////////////////////////

#ifdef MSCSIM_FORCEFEEDBACK
void HID_Joysticks::initForces( int joyNum )
{
#   ifdef MSCSIM_DIRECTINPUT
    LONG  direct [ 2 ] = { 1, 0 };
    DWORD axes   [ 2 ] = { DIJOFS_X, DIJOFS_Y };

    DICONSTANTFORCE constForce[ 2 ];

    constForce[ 0 ].lMagnitude = 0;
    constForce[ 1 ].lMagnitude = 0;

    DIEFFECT diEffect;

    ZeroMemory( &diEffect, sizeof( diEffect ) );

    diEffect.dwSize                  = sizeof( DIEFFECT );
    diEffect.dwFlags                 = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
    diEffect.dwDuration              = INFINITE;
    diEffect.dwSamplePeriod          = 0;
    diEffect.dwGain                  = DI_FFNOMINALMAX;
    diEffect.dwTriggerButton         = DIEB_NOTRIGGER;
    diEffect.dwTriggerRepeatInterval = 0;
    diEffect.cAxes                   = 2;
    diEffect.rgdwAxes                = axes;
    diEffect.rglDirection            = direct;
    diEffect.lpEnvelope              = NULL;
    diEffect.cbTypeSpecificParams    = sizeof( DICONSTANTFORCE );
    diEffect.lpvTypeSpecificParams   = constForce;
    diEffect.dwStartDelay            = 0;

    if (
         FAILED( joysDI[ iJoyNum ]->CreateEffect( GUID_ConstantForce, &diEffect, &( effect[ joyNum ] ), NULL ) )
       )
    {
        cerr << "WARNING: Creating of constant force effect failed." << endl;
        effect[ joyNum ] = 0;
    }

    effect[ joyNum ]->Stop();
#   endif
}
#endif

////////////////////////////////////////////////////////////////////////////////

#ifdef MSCSIM_FORCEFEEDBACK
void HID_Joysticks::updateForces( int joyNum )
{
#   ifdef MSCSIM_DIRECTINPUT
    if ( pEffect[ iJoyNum ] )
    {
        DICONSTANTFORCE constForce[ 2 ];

        constForce[ 0 ].lMagnitude = (long)( forceX[ joyNum ] * (float)DI_FFNOMINALMAX );
        constForce[ 1 ].lMagnitude = (long)( forceY[ joyNum ] * (float)DI_FFNOMINALMAX );

        DIEFFECT diEffect;

        if (
             SUCCEEDED( effect[ joyNum ]->GetParameters( &diEffect, DIEP_ALLPARAMS ) )
           )
        {
            diEffect.cbTypeSpecificParams    = sizeof( DICONSTANTFORCE );
            diEffect.lpvTypeSpecificParams   = constForce;
            
            if (
                 FAILED( pEffect[ joyNum ]->SetParameters( &diEffect, DIEP_TYPESPECIFICPARAMS | DIEP_START ) )
               )
            {
                cerr << "WARNING: Setting parameters of constant force effect failed." << endl;
            }
        }
        else
        {
            cerr << "WARNING: Getting parameters of constant force effect failed." << endl;
        }
    }
#   endif
}
#endif

////////////////////////////////////////////////////////////////////////////////

#ifdef MSCSIM_DIRECTINPUT
BOOL CALLBACK enumJoysCallback( const DIDEVICEINSTANCE * devInst, VOID * context )
{
    HRESULT result;

    LPDIRECTINPUTDEVICE8 joyDI;

    // Obtain an interface to the enumerated joystick.
    result = di->CreateDevice( devInst->guidInstance, &joyDI, NULL );

    if ( SUCCEEDED( result ) && joysCount < MSCSIM_MAX_JOYS_COUNT )
    {
        joysDI[ joysCount ] = joyDI;
        joysCount++;
    }

    if ( joysCount < MSCSIM_MAX_JOYS_COUNT )
    { 
        return DIENUM_CONTINUE;
    }
    else
    {
        return DIENUM_STOP;
    }   
}
#endif

////////////////////////////////////////////////////////////////////////////////

#ifdef MSCSIM_DIRECTINPUT
BOOL CALLBACK enumAxesCallback( const DIDEVICEOBJECTINSTANCE* devObjInst, VOID* context )
{
    LPDIRECTINPUTDEVICE8 joyDI = (LPDIRECTINPUTDEVICE8)context;

    DIPROPRANGE propRangeDI;

    propRangeDI.diph.dwSize       = sizeof( DIPROPRANGE );
    propRangeDI.diph.dwHeaderSize = sizeof( DIPROPHEADER );
    propRangeDI.diph.dwObj        = devObjInst->dwType; // Specify the enumerated axis
    propRangeDI.diph.dwHow        = DIPH_BYID;
    propRangeDI.lMin              = - MSCSIM_AXIS_RANGE;
    propRangeDI.lMax              =   MSCSIM_AXIS_RANGE;

    // Set the range for the axis
    if (
         FAILED( joyDI->SetProperty( DIPROP_RANGE, &propRangeDI.diph ) )
       )
    {
        return DIENUM_STOP;
    }

    return DIENUM_CONTINUE;
}
#endif
